#
#      zak - Transcode and concatinate multiple MP3 files
#
#      Copyright 2008 Joshua M. Hoffman - joshua@joshua.net
#
#      This program is free software; you can redistribute it and/or modify  
#      it under the terms of the GNU General Public License as published by  
#      the Free Software Foundation; either version 2 of the License, or  
#      (at your option) any later version.  
#  
#      This program is distributed in the hope that it will be useful,  
#      but WITHOUT ANY WARRANTY; without even the implied warranty of  
#      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  
#      GNU General Public License for more details.  
#  
#      You should have received a copy of the GNU General Public License  
#      along with this program; if not, write to the Free Software  
#      Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

import pygtk
import gtk
import re
import os
import zak.util
import zak.shell_run
from decimal import *

# gui elements

class Button(gtk.Button):
    
    def __init__(self, label, callback, data=None):
        # initialize the button
        gtk.Button.__init__(self, label)
        if data == None:
            self.connect("clicked", callback)
        else:
            self.connect("clicked", callback, data)
        self.set_size_request(100, -1)


class MakeWarningPopup(gtk.MessageDialog, zak.util.Constants):
    
    def __int__(self, message):
        gtk.MessageDialog.__init__(self.myName, None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR,
            gtk.BUTTONS_CLOSE)
        self.format_secondary_text(message)


class WorkingPopUp(gtk.Window, zak.util.Constants):
    
    def __init__(self, label):
        # initialize the window
        gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL)
        # properties
        self.set_resizable(False)
        self.set_title(self.myName)
        self.set_border_width(2)
        self.set_modal(True)
        self.set_position(gtk.WIN_POS_CENTER)
        self.set_deletable(False)
        # visual elements
        vbox = gtk.VBox(False, 5)
        vbox.set_border_width(10)
        self.add(vbox)
        vbox.show()
        # a label
        myLabel = gtk.Label(label)
        vbox.pack_start(myLabel, False, False, 0)
        myLabel.show()
        # Create a centering alignment object
        align = gtk.Alignment(0.5, 0.5, 0, 0)
        vbox.pack_start(align, False, False, 5)
        align.show()
        # Create the ProgressBar
        self.pbar = gtk.ProgressBar()
        #self.pbar.pulse()
        align.add(self.pbar)
        self.pbar.show()
        # a button
        hbox = gtk.HBox(False, 5)
        vbox.pack_start(hbox, False, False, 5)
        self.button = gtk.Button("Cancel")
        self.button.set_size_request(100, -1)
        self.button.connect("clicked", self.die)
        hbox.pack_start(self.button, True, False, 2)
        hbox.show()
        self.button.show()
        # need to add cancel button that turns to ok when done() is called
        
    def pulse(self, widget=None):
        self.pbar.pulse()
        
    def done(self, widget=None):
        self.pbar.set_fraction(0.0)
        self.pbar.set_text('Done!')
        self.button.set_label('Ok')
        
    def die(self, widget=None):
        self.destroy()
        
        
class MainWindow(gtk.Window, zak.util.Constants, zak.util.Prefs):
    
    def __init__(self):
        # initialized the window
        gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL)
        # set properties
        self.set_title(self.myName)
        # Sets the border width of the window.
        self.set_border_width(20)
        self.set_size_request(600, 450)
        self.set_resizable(False)
        # handle the window closing
        self.connect("delete_event", self._close_window_cb)
        # Create a notebook - thanks Scott!
        self.notebook = gtk.Notebook()
        self.notebook.set_tab_pos(gtk.POS_TOP)
        # define tab 1
        tab1 = gtk.VBox(True, 2)
        label = gtk.Label("Input")
        self.notebook.append_page(tab1, label)
        # define tab 2
        tab2 = gtk.VBox(True, 2)
        label = gtk.Label("Output")
        self.notebook.append_page(tab2, label)
        # define tab 3
        tab3 = gtk.VBox(True, 2)
        label = gtk.Label("Options")
        self.notebook.append_page(tab3, label)
        # add the notebook to the window
        master_vbox = gtk.VBox(False, 2)
        self.add(master_vbox)
        master_vbox.pack_start(self.notebook, False, False, 2)
        # progress bar
        self.pbar = gtk.ProgressBar()
        master_vbox.pack_start(self.pbar, True, True, 5)
        # bottom buttons
        button_hbox = gtk.HBox(True, 2)
        master_vbox.pack_start(button_hbox, False, False, 2)
        # New source select button
        button = zak.gui.Button("Process", self._process_cb)
        button_hbox.pack_end(button, False, False, 2)
        button = zak.gui.Button("Quit", self._close_window_cb)
        button_hbox.pack_end(button, False, False, 2)
        ###################################################################
        # First tab
        #
        hbox_tab1 = gtk.HBox(False, 2)
        tab1.pack_start(hbox_tab1, True, True, 2)
        vbox1_tab1 = gtk.VBox(False, 2)
        vbox2_tab1 = gtk.VBox(False, 2)
        hbox_tab1.pack_start(vbox1_tab1, True, True, 2)
        hbox_tab1.pack_start(vbox2_tab1, False, False, 2)
        # Source files label
        label = gtk.Label('Source MP3 Files:')
        vbox1_tab1.pack_start(label, False, False, 0)
        # scrolled window
        scrolled_window = gtk.ScrolledWindow()
        scrolled_window.set_border_width(1)
        scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS)
        vbox1_tab1.pack_start(scrolled_window, True, True, 0)
        table = gtk.Table(30, 50, True)
        # set the spacing to 10 on x and 10 on y
        table.set_row_spacings(5)
        table.set_col_spacings(5)
        # pack the table into the scrolled window
        scrolled_window.add_with_viewport(table)
        # Create the list
        self.displayList = gtk.ListStore(str)
        self.treeview = gtk.TreeView(self.displayList)
        self.treeview.set_headers_visible(False)
        #tree.set_model(fileList)
        table.attach(self.treeview, 0, 50, 0, 30)
        #vbox1_tab1.pack_start(self.treeview, False, False, 0)
        fileNameColumn = gtk.TreeViewColumn()
        self.treeview.append_column(fileNameColumn)
        cell = gtk.CellRendererText()
        fileNameColumn.pack_start(cell, True)
        # set the cell "text" attribute to column 0 - retrieve text
        #+from that column in treestore
        fileNameColumn.add_attribute(cell, 'text', 0)
        # Allow drag and drop reordering of rows
        self.treeview.set_reorderable(True)
        self.treeselection = self.treeview.get_selection()
        self.treeselection.set_mode(gtk.SELECTION_MULTIPLE)
        # Button Sidebar
        #button = gtk.Button("Add")
        button = zak.gui.Button("Add", self._file_buttons_cb, "add")
        #button.connect("clicked", self._file_buttons_cb, "add")
        #button.set_size_request(100, -1)
        vbox2_tab1.pack_start(button, True, False, 2)
        #button = gtk.Button("Remove")
        button = zak.gui.Button("Remove", self._file_buttons_cb, "remove")
        #button.set_size_request(100, -1)
        #button.connect("clicked", self._file_buttons_cb, "remove")
        vbox2_tab1.pack_start(button, True, False, 2)
        ###################################################################
        # Second tab
        #
        vbox_tab2 = gtk.VBox(False, 0)
        hbox_tab2 = gtk.HBox(True, 2)
        tab2.pack_start(vbox_tab2, False, False, 2)
        vbox_tab2.pack_start(hbox_tab2, False, False, 2)
        vbox1_tab2 = gtk.VBox(False, 2)
        vbox2_tab2 = gtk.VBox(False, 2)                
        hbox_tab2.pack_start(vbox1_tab2, False, True, 2)
        hbox_tab2.pack_start(vbox2_tab2, False, True, 2)
        # Output file
        output_file_frame = gtk.Frame("Destination File:")
        vbox1_tab2.pack_start(output_file_frame, True, True, 0)
        output_file_vbox = gtk.VBox(False, 0)
        output_file_frame.add(output_file_vbox)
        #fileSelectButton = gtk.Button("Select")
        fileSelectButton = zak.gui.Button("Select", self._file_select_button_cb)
        #fileSelectButton.connect("clicked", self._file_select_button_cb)
        #fileSelectButton.set_size_request(100, -1)
        self.recordFileEntry = gtk.Entry()
        self.recordFileEntry.set_text(self.get_pref("output_file"))
        output_file_vbox.pack_start(self.recordFileEntry, False, False, 0)
        button_hbox = gtk.HBox(False, 5)
        button_hbox.pack_start(fileSelectButton, True, False, 5)
        output_file_vbox.pack_start(button_hbox, True, False, 0)
        # output file options
        output_opts_frame = gtk.Frame('Options:')
        vbox2_tab2.pack_start(output_opts_frame, True, False, 2)
        output_opts_vbox = gtk.VBox(False, 0)
        output_opts_frame.add(output_opts_vbox)
        # format
        format_label = gtk.Label('Format:')
        self.format_combo = gtk.combo_box_new_text()
        self.format_combo.set_size_request(100, -1)
        self.format_combo.append_text('m4a/m4b')
        self.format_combo.append_text('mp3')
        self.format_combo.set_active(0)
        format_hbox = gtk.HBox(False, 0)
        format_hbox.pack_start(format_label, True, False, 0)
        format_hbox.pack_start(self.format_combo, True, False, 0)
        output_opts_vbox.pack_start(format_hbox, True, False, 0)
        # bitrate
        bitrate_label = gtk.Label('Bitrate:')
        self.bitrate_combo = gtk.combo_box_new_text()
        self.bitrate_combo.set_size_request(100, -1)
        self.bitrate_combo.append_text('64k')
        self.bitrate_combo.append_text('96k')
        self.bitrate_combo.append_text('128k')
        self.bitrate_combo.append_text('192k')
        self.bitrate_combo.set_active(0)
        bitrate_hbox = gtk.HBox(False, 0)
        bitrate_hbox.pack_start(bitrate_label, True, False, 0)
        bitrate_hbox.pack_start(self.bitrate_combo, True, False, 0)
        output_opts_vbox.pack_start(bitrate_hbox, True, False, 0)
        # samplerate
        samplerate_label = gtk.Label('Frequency:')
        self.samplerate_combo = gtk.combo_box_new_text()
        self.samplerate_combo.set_size_request(100, -1)
        self.samplerate_combo.append_text('22050')
        self.samplerate_combo.append_text('44100')
        self.samplerate_combo.set_active(0)
        samplerate_hbox = gtk.HBox(False, 0)
        samplerate_hbox.pack_start(samplerate_label, True, False, 0)
        samplerate_hbox.pack_start(self.samplerate_combo, True, False, 0)
        output_opts_vbox.pack_start(samplerate_hbox, True, False, 0)
        # channels
        channel_label = gtk.Label('Mode:')
        self.channel_combo = gtk.combo_box_new_text()
        self.channel_combo.set_size_request(100, -1)
        self.channel_combo.append_text('Stereo')
        self.channel_combo.append_text('Mono')
        self.channel_combo.set_active(0)
        channel_hbox = gtk.HBox(False, 0)
        channel_hbox.pack_start(channel_label, True, False, 0)
        channel_hbox.pack_start(self.channel_combo, True, False, 0)
        output_opts_vbox.pack_start(channel_hbox, True, False, 0)
        # metadata containers
        meta_vbox = gtk.VBox(False, 0)
        vbox_tab2.pack_start(meta_vbox, False, False, 2)
        meta_frame = gtk.Frame('Metadata:')
        meta_vbox.pack_start(meta_frame, True, False, 0)
        meta_hbox = gtk.HBox(True, 0)
        meta_vbox1 = gtk.VBox(False, 0)
        meta_vbox2 = gtk.VBox(False, 0)
        meta_frame.add(meta_hbox)
        meta_hbox.pack_start(meta_vbox1, True, False, 0)
        meta_hbox.pack_start(meta_vbox2, True, False, 0)
        # metadata widgets
        title_label = gtk.Label("Title:")
        self.titleEntry = gtk.Entry()
        self.titleEntry.set_text(self.get_meta("title"))
        meta_vbox1.pack_start(title_label, True, False, 0)
        meta_vbox1.pack_start(self.titleEntry, True, False, 0)
        author_label = gtk.Label("Author:")
        self.authorEntry = gtk.Entry()
        self.authorEntry.set_text(self.get_meta("title"))
        meta_vbox1.pack_start(author_label, True, False, 0)
        meta_vbox1.pack_start(self.authorEntry, True, False, 0)
        comment_label = gtk.Label("Comment:")
        self.commentEntry = gtk.Entry()
        self.commentEntry.set_text(self.get_meta("comment"))
        meta_vbox1.pack_start(comment_label, True, False, 0)
        meta_vbox1.pack_start(self.commentEntry, True, False, 0)
        album_label = gtk.Label("Album:")
        self.albumEntry = gtk.Entry()
        self.albumEntry.set_text(self.get_meta("album"))
        meta_vbox2.pack_start(album_label, True, False, 0)
        meta_vbox2.pack_start(self.albumEntry, True, False, 0)
        track_label = gtk.Label("Track:")
        self.trackEntry = gtk.Entry()
        self.trackEntry.set_text(self.get_meta("track"))
        meta_vbox2.pack_start(track_label, True, False, 0)
        meta_vbox2.pack_start(self.trackEntry, True, False, 0)
        year_label = gtk.Label("Year:")
        self.yearEntry = gtk.Entry()
        self.yearEntry.set_text(self.get_meta("year"))
        meta_vbox2.pack_start(year_label, True, False, 0)
        meta_vbox2.pack_start(self.yearEntry, True, False, 0)
        ###################################################################
        # third tab
        #
        vbox_tab3 = gtk.VBox(True, 0)
        tab3.pack_start(vbox_tab3, True, False, 2)
        progloc_frame = gtk.Frame('Program Locations:')
        vbox_tab3.pack_start(progloc_frame, True, False, 2)
        progloc_frame_vbox = gtk.VBox(True, 0)
        progloc_frame.add(progloc_frame_vbox)
        # madplay
        madplay_label = gtk.Label('madplay:')
        madplay_hbox = gtk.HBox(False, 2)
        self.madplayEntry = gtk.Entry()
        self.madplayEntry.set_text(self.get_pref("madplay"))
        button = zak.gui.Button("Select", self._activity_cb)
        madplay_hbox.pack_start(madplay_label, False, False, 2)
        madplay_hbox.pack_start(self.madplayEntry, True, True, 2)
        madplay_hbox.pack_start(button, False, False, 2)
        progloc_frame_vbox.pack_start(madplay_hbox, False, False, 2)
        # ffmpeg
        ffmpeg_label = gtk.Label('ffmpeg:')
        ffmpeg_hbox = gtk.HBox(False, 2)
        self.ffmpegEntry = gtk.Entry()
        self.ffmpegEntry.set_text(self.get_pref("ffmpeg"))
        button = zak.gui.Button("Select", self._activity_cb)
        ffmpeg_hbox.pack_start(ffmpeg_label, False, False, 2)
        ffmpeg_hbox.pack_start(self.ffmpegEntry, True, True, 2)
        ffmpeg_hbox.pack_start(button, False, False, 2)
        progloc_frame_vbox.pack_start(ffmpeg_hbox, False, False, 2)
        # debug mode
        debug_frame = gtk.Frame('Debugging:')
        vbox_tab3.pack_start(debug_frame, False, False, 2)
        debug_frame_vbox = gtk.VBox(False, 2)
        debug_frame.add(debug_frame_vbox)
        terminal_hbox = gtk.HBox(False, 2)
        debug_frame_vbox.pack_start(terminal_hbox, False, False, 2)
        self.showTerminal = gtk.CheckButton()
        terminal_label = gtk.Label("Show terminal while processing files")
        terminal_hbox.pack_start(self.showTerminal, False, False, 2)
        terminal_hbox.pack_start(terminal_label, False, False, 2)
        
        # show it
        self.show_all()
        ###################################################################
        # gui callbacks
        #
    def _file_select_button_cb(self, button):
        self.fileBrowser = gtk.FileSelection("Select Destination File...")
        self.fileBrowser.set_modal(True)
        #self.fileBrowser.connect("destroy", self.delete_event)
        self.fileBrowser.ok_button.connect("clicked", self._fileBrowser_cb)
        self.fileBrowser.cancel_button.connect("clicked", lambda w: self.fileBrowser.destroy())
        # Set the filename
        self.fileBrowser.set_filename(self.recordFileEntry.get_text())
        self.fileBrowser.show()
        
    def _fileBrowser_cb(self, widget):
        filename = self.fileBrowser.get_filename()
        self.recordFileEntry.set_text(filename)
        self.fileBrowser.destroy()
    
    def _close_window_cb(self, button, data=None):
        gtk.main_quit()
        
    def _finished_cb(self, widget):
        print '* Done!'

    def _activity_cb(self, widget):
        print '* Activity!'

    def _file_buttons_cb(self, button, action):
        if action == "add":
            chooser = gtk.FileChooserDialog(title='Add Source MP3 Files...',action=gtk.FILE_CHOOSER_ACTION_OPEN,buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK))
            chooser.set_select_multiple(True)
            response = chooser.run()
            if response == gtk.RESPONSE_OK:
                files = chooser.get_filenames()
                for f in files:
                    self.displayList.append([f])
    
            elif response == gtk.RESPONSE_CANCEL:
                print 'Closed, no files selected'
            chooser.destroy()

        elif action == "remove":
            _selection = self.treeselection.get_selected_rows()
            _count = 0
            for _path in _selection[1]:
                _index = int(_path[0])
                _index = _index - _count
                _row = self.displayList[_index]
                _file = _row[0]
                # remove it from the list
                del self.displayList[_index]
                # increment count as each del shifts the list
                _count += 1
                print 'DEBUG: ' + _file + ' removed!'
    
    def _process_cb(self, widget):
        # update the prefs
        self._update_prefs()
        # update the metadata
        self._update_metadata()
        
        if self._valid_prefs():
            if self._valid_infiles():
                self.exptected_size = self._get_expected_size()
                command = self._build_command()
                self.pbar.set_text('Preparing...')
                processor = zak.shell_run.ShellRun(self.pulse, self.done)
                pid = processor.run_command(command)
                print pid
        
    def _get_expected_size(self):
        # guesstimate the final file size
        infiles = self.get_input_files()
        total_size = 0
        for file in infiles:
            f = os.stat(file)
            total_size += f.st_size
        total_size = int(total_size * 1.03)
        
        return total_size
        
    def _build_command(self):
        # program paths
        madplay = self.get_pref('madplay')
        ffmpeg = self.get_pref('ffmpeg')
        # get the input files
        infiles = self.get_input_files()
        # grab a file and determine its sample freq
        testfile = infiles[0]
        audiofile = zak.util.AudioFile(testfile)
        if not audiofile.working and audiofile.valid:
            infreq = int(audiofile.rate)
            inchannels = int(audiofile.channels)
        
        del audiofile
        
        infiles = [re.escape(file) for file in infiles]
        filelist = ''
        for file in infiles:
            filelist += file + ' '
        # the outfile
        outfile = self.get_pref("output_file")

        # assemble the command
        command = '(' + madplay + ' ' + filelist + ' --output=raw:- 2>/dev/null'
        # get output settings
        outchannels = self.get_pref('output_channels')
        if outchannels == 'Stereo':
            outchannels = 2
        else:
            outchannels = 1
            
        outbitrate = self.get_pref('output_bitrate')
        outfreq = int(self.get_pref('output_samplerate'))
        codec = self.get_pref('output_format')
        if codec == 'mp3':
            fileformat = 'mp3'
        else:
            fileformat = 'mp4'
            codec = 'aac'
        
        
        command += ' | ' + ffmpeg + ' -f s16le -ac %d -ar %d -i - -ab %s -ac %d -acodec %s -ar %d -f %s' % (
                                           inchannels, infreq, outbitrate, outchannels, codec,
                                           outfreq, fileformat)
        
        # get metadata
        title = self.get_meta('title')
        author = self.get_meta('author')
        comment = self.get_meta('comment')
        album = self.get_meta('album')
        track = self.get_meta('track')
        year = self.get_meta('year')
        # apply the metadata
        if not title == "":
            command += ' -title "%s"' % title
        if not author == "":
            command += ' -author "%s"' % author
        if not comment == "":
            command += ' -comment "%s"' % comment
        if not album == "":
            command += ' -album "%s"' % album
        if not track == "":
            command += ' -track "%s"' % track
        if not year == "":
            command += ' -year "%s"' % year
        
        # and the output file
        outfile = self.get_pref("output_file")
        command += ' ' + outfile + ')\nexit\n'
        
        return command
        
    def _update_prefs(self):
        # get and update prefs from ui choices
        self.set_pref('output_file', self.recordFileEntry.get_text())
        self.set_pref("output_format", self.format_combo.get_active_text())
        self.set_pref("output_bitrate", self.bitrate_combo.get_active_text())
        self.set_pref("output_channels", self.channel_combo.get_active_text())
        self.set_pref("output_samplerate", self.samplerate_combo.get_active_text())
        self.set_pref("madplay", self.madplayEntry.get_text())
        self.set_pref("ffmpeg", self.ffmpegEntry.get_text())
        # input files
        self.clear_input_files()
        _count = len(self.displayList)
        for _n in range(_count):
            _row = self.displayList[_n]
            _name = _row[0]
            self.add_input_file(_name)
            
    def _update_metadata(self):
        self.set_meta("title", self.titleEntry.get_text())
        self.set_meta("author", self.authorEntry.get_text())
        self.set_meta("comment", self.commentEntry.get_text())
        self.set_meta("album", self.albumEntry.get_text())
        self.set_meta("track", self.trackEntry.get_text())
        self.set_meta("year", self.yearEntry.get_text())
        
    def pulse(self, widget=None):
        getcontext().prec = 2
        outfile = outfile = self.get_pref("output_file")
        
        try:
            outfile = os.stat(outfile)
            outfile_size = outfile.st_size
        except OSError:
            outfile_size = 0
            
        expected_size = self.exptected_size
        fraction = Decimal(outfile_size) / Decimal(expected_size)
        fraction = float(fraction)
        status_msg = 'Processing %d bytes' % outfile_size
        self.pbar.set_text(status_msg)
        self.pbar.set_fraction(fraction)
    
    def _check_file(self, file):
        # return true if file exists
        exists = True
        try:
            file = os.stat(file)
        except OSError:
            exists = False
        return exists
    
    def _valid_prefs(self):
        # check for madplay
        madplay = self.get_pref('madplay')
        if not self._check_file(madplay):
            message = "No madplay found!\nPlease check the path in the Options tab."
            dialog = self._make_error_popup(message)
            dialog.run()
            dialog.destroy()
            return False
        # check for ffmpeg
        ffmpeg = self.get_pref('ffmpeg')
        if not self._check_file(ffmpeg):
            message = "No ffmpeg found!\nPlease check the path in the Options tab."
            dialog = self._make_error_popup(message)
            dialog.run()
            dialog.destroy()
            return False
        # check for input files
        infiles = self.get_input_files()
        if len(infiles) == 0:
            message = "No source MP3 files!\nPlease add some in the Input tab."
            dialog = self._make_error_popup(message)
            dialog.run()
            dialog.destroy()
            return False
        # check for the output file
        outfile = outfile = self.get_pref("output_file")
        if self._check_file(outfile):
            message = "Output file already exists!"
            dialog = self._make_error_popup(message)
            dialog.run()
            dialog.destroy()
            return False
        return True
    
    def _valid_infiles(self):
        # are the input files actually mp3s?
        infiles = self.get_input_files()
        for file in infiles:
            audiofile = zak.util.AudioFile(file)
            if not audiofile.working and audiofile.valid:
                if not audiofile.mimetype == 'application/x-id3':
                    del audiofile
                    message = "%s\ndoes not appear to be a valid MP3 file." % file
                    dialog = self._make_error_popup(message)
                    dialog.run()
                    dialog.destroy()
                    return False
        return True
                
    def _make_error_popup(self, message):
        dialog = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR,
        gtk.BUTTONS_CLOSE, "Error")
        dialog.set_title(self.myName)
        dialog.format_secondary_text(message)
        return dialog
    
    def done(self, widget=None):
        self.pbar.set_fraction(1.0)
        self.pbar.set_text('Done!')
